using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Text;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Linq;

namespace Project.Tool
{
    /// <summary>
    /// 通信帮助类
    /// </summary>
	public class SocketHelper
	{

		/// <summary>
		/// 客户端管理
		/// </summary>
		private Dictionary<Socket, ClientInfo> clientPool = new Dictionary<Socket,ClientInfo> ();

		/// <summary>
		/// 离线消息列表
		/// </summary>
		private List<SocketMessage> message = new List<SocketMessage> ();

        /// <summary>
        /// 实例一个Socket
        /// </summary>
        /// <param name="prot">端口号</param>
        /// <param name="socketListen">限制数</param>
        public SocketHelper(int prot,int socketListen)
        {
            NewSocketStart(prot,socketListen).Start();
        }

        /// <summary>
        /// 启动
        /// </summary>
        /// <param name="prot">端口号</param>
        /// <param name="socketListen">请求队列限制数</param>
        private Thread NewSocketStart(int prot, int socketListen)
		{
			return new Thread (() => {

				Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

				IPEndPoint iepEndPoint = new IPEndPoint(IPAddress.Any, prot); 
                
				socket.Bind(iepEndPoint);

				socket.Listen(socketListen);

				socket.BeginAccept(new AsyncCallback(Accept), socket);

			});
		}

		/// <summary>
		/// 处理客户端连接请求
		/// </summary>
		/// <param name="ia"></param>
		private void Accept (IAsyncResult ia)
		{
			Socket socket = ia.AsyncState as Socket;

			// 创建客户端实例
			var client = socket.EndAccept(ia);

			// 接受传入的链接
			socket.BeginAccept(new AsyncCallback(Accept), socket);

			try {

                byte[] buffer = new byte[1024];

                // 开启接收数据
                client.BeginReceive (buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback (Receive), client);

                // 记录连接客户
                AddClient(client, buffer);

                // 客户端标识
                string sessionId = client.RemoteEndPoint.ToString () + " - " + client.Handle.ToString ();

				Console.WriteLine ("Client ({0}) Connected", sessionId);

			} catch (Exception ex) {

				Console.WriteLine ("Error：\r\n" + ex.ToString ());

			}
		}

		/// <summary>
		/// 处理客户端发送的消息
		/// </summary>
		/// <param name="ia"></param>
		private void Receive (IAsyncResult ia)
		{
            Socket client = ia.AsyncState as Socket;

			// 检查是否存在客户端
			if (client == null || !clientPool.ContainsKey (client)) return;

			int length = client.EndReceive(ia);

			if (length > 0) {

                // 用户发送消息
                MessageInstance(client,length);

            } else {

				// 用户退出
				DeleteClient(client);

			}
		}

		/// <summary>
		/// 消息广播
		/// </summary>
		private void NewBoardcast(string Message)
		{
            byte[] template = PackData(Message);

            foreach (var item in clientPool)
            {
                Socket client = item.Key;
                client.Send(template, template.Length, SocketFlags.None);
            }
        }

		#region 消息处理函数

		/// <summary>
		/// 统一接受消息
		/// </summary>
		private void MessageInstance(Socket client,int length)
		{

			var clientInfo = clientPool[client];

			// 接收消息
			client.BeginReceive(clientInfo.buffer, 0, clientInfo.buffer.Length, SocketFlags.None, new AsyncCallback(Receive), client);

			// 接受到的消息
			string context = Encoding.UTF8.GetString (clientInfo.buffer, 0, length);

			// 客户端发起链接请求
			if ( context.Contains(webSocket) )
			{
                // 返回和服务端握手数据
                client.Send(PackHandShakeData(GetSecKeyAccetp(clientInfo.buffer, length)));
            }
			else {

				// 不明白 需要测试
				context = AnalyticData(clientInfo.buffer, clientInfo.buffer.Length);

                if (clientPool[client] != null)
                {

                    SocketMessage socketMessage = new SocketMessage();
                    socketMessage.userInfo = clientPool[client];

                    socketMessage.sendMessage = context;
                    // Context [要求JSON数据 > SocketMessage 模型]

                    // 写入消息信息
                    // PushMessage(socketMessage);

                    // 发送消息
                    SendMessage(socketMessage);

                }
                else {
                    // 下线用户抛出
                    var buf = PackData("您已断开连接");
                    client.Send(buf, buf.Length, SocketFlags.None);
                }

				
			}

			
			
		}

		/// <summary>
		/// 添加一个消息
		/// </summary>
		/// <param name="context"></param>
		private void PushMessage(SocketMessage model)
		{
			message.Add(model);
			Console.WriteLine($"sendName:{model.userInfo.userName},toUserKey:{model.toUserKey},Message:{model.sendMessage}");
		}

		/// <summary>
		/// 消息队列
		/// </summary>
		/// <param name="message"></param>
		private void SendMessage(SocketMessage message)
		{
			// 需要发送的客户机
			var toClient = clientPool.Where(a => a.Value.userKey == message.toUserKey).ToList();

			// 对方未登陆
			if ( toClient == null || toClient.Count < 1 )
			{
				// 加入离线消息列表
				PushMessage(message);
			}
			else {

				foreach ( var item in toClient )
				{
					var buf = PackData(message.sendMessage);
					item.Key.Send( buf, buf.Length, SocketFlags.None );
				}

			}

		}

		#endregion

		#region 客户机信息维护管理

		/// <summary>
		/// 添加客户机
		/// </summary>
		private void AddClient(Socket client, byte[] buffer)
		{
			clientPool.Add(
				client,
				new ClientInfo {
					handle = client.Handle,
					userPoint = client.RemoteEndPoint,
                    buffer = buffer
                }
			);
		}

		/// <summary>
		/// 修改客户机信息
		/// </summary>
		private void UpdateClientInfo(Socket client,ClientInfo clientInfo)
		{
            // 修改忽略地址与句柄
			clientInfo.handle = client.Handle;
			clientInfo.userPoint = client.RemoteEndPoint;

            // 修改用户信息
			clientPool[client] = clientInfo;

            // 查看是否在其他地方登陆
            var clients = clientPool.Where( a => (
                a.Value.userKey == clientInfo.userKey && 
                a.Value.token == clientInfo.token &&
                a.Value.clientType == clientInfo.clientType && 
                a.Value.projectToken == clientInfo.projectToken &&
                a.Key != client
                ));

            foreach (var item in clients) DeleteClient(item.Key);

        }

		/// <summary>
		/// 客户机下线
		/// </summary>
		private void DeleteClient(Socket client)
		{
			client.Disconnect(true);
			clientPool.Remove(client);
			Console.WriteLine("Client ({0}) Disconnet", client.RemoteEndPoint.ToString() + " - " + client.Handle.ToString());
		}

		#endregion

		#region 客户端发送消息处理

		/// <summary>
		/// 解析客户端发送来的数据
		/// </summary>
		/// <param name="recBytes"></param>
		/// <param name="recByteLength"></param>
		/// <returns></returns>
		private string AnalyticData(byte[] recBytes, int recByteLength)
		{
			if ( recByteLength < 2 )
			{
				return string.Empty;
			}

			bool fin = (recBytes [0] & 0x80) == 0x80; // 1bit，1表示最后一帧  
			if ( !fin )
			{
				return string.Empty;// 超过一帧暂不处理 
			}

			bool mask_flag = (recBytes [1] & 0x80) == 0x80; // 是否包含掩码  
			if ( !mask_flag )
			{
				return string.Empty;// 不包含掩码的暂不处理
			}

			int payload_len = recBytes [1] & 0x7F; // 数据长度  

			byte[] masks = new byte[4];
			byte[] payload_data;

			if ( payload_len == 126 )
			{
				Array.Copy(recBytes, 4, masks, 0, 4);
				payload_len = (UInt16) ( recBytes[2] << 8 | recBytes[3] );
				payload_data = new byte[payload_len];
				Array.Copy(recBytes, 8, payload_data, 0, payload_len);

			}
			else if ( payload_len == 127 )
			{
				Array.Copy(recBytes, 10, masks, 0, 4);
				byte[] uInt64Bytes = new byte[8];
				for ( int i = 0; i < 8; i++ )
				{
					uInt64Bytes[i] = recBytes[9 - i];
				}
				UInt64 len = BitConverter.ToUInt64 (uInt64Bytes, 0);

				payload_data = new byte[len];
				for ( UInt64 i = 0; i < len; i++ )
				{
					payload_data[i] = recBytes[i + 14];
				}
			}
			else {
				Array.Copy(recBytes, 2, masks, 0, 4);
				payload_data = new byte[payload_len];
				Array.Copy(recBytes, 6, payload_data, 0, payload_len);

			}

			for ( var i = 0; i < payload_len; i++ )
			{
				payload_data[i] = (byte) ( payload_data[i] ^ masks[i % 4] );
			}

			return Encoding.UTF8.GetString(payload_data);
		}

		/// <summary>
		/// 内容处理
		/// </summary>
		/// <param name="message">处理内容</param>
		/// <returns></returns>
		private byte[] PackData(string message)
		{
			byte[] contentBytes = null;
			byte[] temp = Encoding.UTF8.GetBytes (message);

			if ( temp.Length < 126 )
			{
				contentBytes = new byte[temp.Length + 2];
				contentBytes[0] = 0x81;
				contentBytes[1] = (byte) temp.Length;
				Array.Copy(temp, 0, contentBytes, 2, temp.Length);
			}
			else if ( temp.Length < 0xFFFF )
			{
				contentBytes = new byte[temp.Length + 4];
				contentBytes[0] = 0x81;
				contentBytes[1] = 126;
				contentBytes[2] = (byte) ( temp.Length & 0xFF );
				contentBytes[3] = (byte) ( temp.Length >> 8 & 0xFF );
				Array.Copy(temp, 0, contentBytes, 4, temp.Length);
			}
			else {
				// 暂不处理超长内容  
			}

			return contentBytes;
		}

		#endregion

		#region 打包服务器握手字符串

		/// <summary>
		/// 打包服务器握手数据
		/// </summary>
		/// <param name="secKeyAccept"></param>
		/// <returns></returns>
		private static byte[] PackHandShakeData(string secKeyAccept)
		{
			var responseBuilder = new StringBuilder ();
			responseBuilder.Append("HTTP/1.1 101 Switching Protocols" + "\r\n");
			responseBuilder.Append("Upgrade: websocket" + "\r\n");
			responseBuilder.Append("Connection: Upgrade" + "\r\n");
			responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + "\r\n\r\n");

			return Encoding.UTF8.GetBytes(responseBuilder.ToString());
		}

		/// <summary>
		/// 打包服务器握手秘钥
		/// </summary>
		/// <param name="handShakeBytes"></param>
		/// <param name="bytesLength"></param>
		/// <returns></returns>
		private static string GetSecKeyAccetp(byte[] handShakeBytes, int bytesLength)
		{
			string handShakeText = Encoding.UTF8.GetString (handShakeBytes, 0, bytesLength);
			string key = string.Empty;
			Regex r = new Regex (@"Sec\-WebSocket\-Key:(.*?)\r\n");
			Match m = r.Match (handShakeText);
			if ( m.Groups.Count != 0 )
			{
				key = Regex.Replace(m.Value, @"Sec\-WebSocket\-Key:(.*?)\r\n", "$1").Trim();
			}
			byte[] encryptionString = SHA1.Create ().ComputeHash (Encoding.ASCII.GetBytes (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
			return Convert.ToBase64String(encryptionString);
		}

		#endregion

		#region 相关数据模型

		/// <summary>
		/// 连接客户机信息
		/// </summary>
		private class ClientInfo
		{
			/// <summary>
			/// 用户连接网络地址
			/// </summary>
			public EndPoint userPoint { get; set; }

			/// <summary>
			/// 指针或句柄
			/// </summary>
			public IntPtr handle { get; set; }

			/// <summary>
			/// 用户标识
			/// </summary>
			public int userKey { get; set; }

			/// <summary>
			/// 用户秘钥
			/// </summary>
			public string token { get;  set; }

			/// <summary>
			/// 用户名
			/// </summary>
			public string userName { get; set; }

			/// <summary>
			/// 客户端类型
			/// </summary>
			public ClientType clientType { get; set; }

			/// <summary>
			/// 消息缓冲区
			/// </summary>
			public byte[] buffer;

            #region 项目身份

            /// <summary>
            /// 用户账户登录名[手机号码]
            /// </summary>
            public string userPhone { get; set; }

            /// <summary>
            /// 用户Token
            /// </summary>
            public string userToken { get; set; }

            /// <summary>
            /// 项目Token
            /// </summary>
            public string projectToken { get; set; }

            #endregion
        }

		/// <summary>
		/// 客户机消息模型
		/// </summary>
		private class SocketMessage
		{
			/// <summary>
			/// 发送方信息
			/// </summary>
			public ClientInfo userInfo { get; set; }

			/// <summary>
			/// 对方Key
			/// </summary>
			public int toUserKey { get; set; }

			/// <summary>
			/// 发送消息客户端
			/// </summary>
			public ClientType clientType { get; set; }

            /// <summary>
            /// 发送的消息
            /// </summary>
            public string sendMessage { get; set; } = "";

			/// <summary>
			/// 发送时间
			/// </summary>
			public DateTime dateTime { get; set; } = DateTime.Now;

			/// <summary>
			/// 发送状态:默认TRUE [TRUE - 消息将被即时发出去][FALSE - 消息将被搁置，等待接收方上线即收到离线消息]
			/// </summary>
			public bool state { get; set; } = true;

			/// <summary>
			/// 消息是否阅读
			/// </summary>
			public bool read { get; set; } = false;
		}

		/// <summary>
		/// 客户机类型
		/// </summary>
		private enum ClientType
		{
			Ios,
			Android,
			Web
		}

		#endregion

		#region 基础参数配置

		/// <summary>
		/// 缓冲区默认字节
		/// </summary>
		private byte[] buf = new byte[1024];

		/// <summary>
		/// 请求头关键字
		/// </summary>
		private const string webSocket = "Sec-WebSocket-Key";

        #endregion

    }
}

